(**
 * The typed lambda calculus
 * @copyright (C) 2021 SML# Development Team.
 * @author Atsushi Ohori
 * @author Liu Bochao
 * @author Katsuhiro Ueno
 *)
structure TypedLambda =
struct

  local
    open SMLFormat.FormatExpression
    fun N0 x = [Guard (SOME {cut = true, strength = 0, direction = Neutral}, x)]
  in
  fun iftrue (x, y) true = x
    | iftrue (x, y) false = y
  fun ifsome (x, y) (SOME _) = x
    | ifsome (x, y) NONE = y
  fun ifcons (x, y) (_::_) = x
    | ifcons (x, y) nil = y
  fun ifsingle (x, y) [_] = x
    | ifsingle (x, y) _ = y
  fun N0ifnotsingle x [_] = x
    | N0ifnotsingle x _ = N0 x
  fun N0ifcons x (_::_) = N0 x
    | N0ifcons x _ = x
  end

  (*%
    @prefix helper_
    @formatter(RecordLabel.label) RecordLabel.format_label
   *)
  type 'value RecordLabelMap =
      (*%
        @prefix helper_
        @format(field fields)
        fields(field)("," +1)
        @format:field(label * value)
        { label +d "=" 2[ +1 value ] }
       *)
      (RecordLabel.label * 'value) list

  val helper_RecordLabelMap =
      fn formatter => fn map =>
         helper_RecordLabelMap formatter (RecordLabel.Map.listItemsi map)

  type loc = Loc.loc

  (*%
    @formatter(Types.ty) Types.format_ty
   *)
  (*% @prefix formatWithType_
    @formatter(Types.ty) Types.format_ty
   *)
  type ty =
      (*%
        @format(ty) ty
       *)
      (*% @prefix formatWithType_
        @format(ty) ty
       *)
      Types.ty

  (*%
    @formatter(Types.btvEnv) Types.format_btvEnv
   *)
  (*% @prefix formatWithType_
    @formatter(Types.btvEnv) Types.format_btvEnv
   *)
  type btvEnv =
      (*%
        @format(btv)
       *)
      (*% @prefix formatWithType_
        @format(btv) btv
       *)
      Types.btvEnv

  (*%
    @formatter(Symbol.longsymbol) Symbol.format_longsymbol
    @formatter(VarID.id) VarID.format_id
    @formatter(ifcons) ifcons
   *)
  (*%
    @prefix formatWithType_
    @formatter(Symbol.longsymbol) Symbol.format_longsymbol
    @formatter(VarID.id) VarID.format_id
    @formatter(ifcons) ifcons
   *)
  type varInfo =
      (*%
        @format({path, id, ty})
        path:ifcons()(path "($" id ")", "$" id)
       *)
      (*%
        @prefix formatWithType_
        @format({path, id, ty})
        L2{ path:ifcons()(path "($" id ")", "$" id) +1 ":" +d ty }
       *)
      {path : Symbol.longsymbol, id : VarID.id, ty : ty}

  (*%
    @formatter(Types.exVarInfo) Types.format_exVarInfo
   *)
  (*% @prefix formatWithType_
    @formatter(Types.exVarInfo) Types.formatWithType_exVarInfo
   *)
  type exVarInfo =
      (*%
        @format(x) x
       *)
      (*% @prefix formatWithType_
        @format(x) x
       *)
      Types.exVarInfo

  (*%
    @formatter(Types.btvEnv) Types.format_btvEnv
    @formatter(ifsingle) ifsingle
    @formatter(N0ifnotsingle) N0ifnotsingle
   *)
  type primTy =
      (*%
       * @format({boundtvars, argTyList: argTy argTys, resultTy})
       * "[" !N0{
       *   { boundtvars } "." +1
       *   R4{
       *     argTys:ifsingle()(,"{")
       *     argTys:N0ifnotsingle()(argTys(argTy)("," +1))
       *     argTys:ifsingle()(,"}")
       *     +1 "->" +d
       *     resultTy
       *   }
       * } "]"
       *)
      {boundtvars : Types.btvEnv, argTyList : ty list, resultTy : ty}

  (*%
    @formatter(BuiltinPrimitive.primitiveTypedLambda)
    BuiltinPrimitive.format_primitiveTypedLambda
   *)
  (*%
    @prefix formatWithType_
    @formatter(BuiltinPrimitive.primitiveTypedLambda)
    BuiltinPrimitive.format_primitiveTypedLambda
    @formatter(primTy) format_primTy
   *)
  type primInfo =
      (*%
       * @format({primitive, ty})
       * primitive
       *)
      (*%
       * @prefix formatWithType_
       * @format({primitive, ty})
       * L2{ primitive +1 ":" +d ty }
       *)
      {primitive : BuiltinPrimitive.primitiveTypedLambda, ty : primTy}

  (*%
    @formatter(BuiltinPrimitive.cast) BuiltinPrimitive.format_cast
   *)
  datatype cast = datatype BuiltinPrimitive.cast

  (*%
   * @formatter(Int8.int) ConstFormat.format_int8_dec_ML
   * @formatter(Int16.int) ConstFormat.format_int16_dec_ML
   * @formatter(Int32.int) ConstFormat.format_int32_dec_ML
   * @formatter(Int64.int) ConstFormat.format_int64_dec_ML
   * @formatter(Word8.word) ConstFormat.format_word8_hex_ML
   * @formatter(Word16.word) ConstFormat.format_word16_hex_ML
   * @formatter(Word32.word) ConstFormat.format_word32_hex_ML
   * @formatter(Word64.word) ConstFormat.format_word64_hex_ML
   * @formatter(Word8.word) ConstFormat.format_word8_hex_ML
   * @formatter(char) ConstFormat.format_char_ML
   *)
  (*%
   * @prefix formatWithType_
   * @formatter(Int8.int) ConstFormat.format_int8_dec_ML
   * @formatter(Int16.int) ConstFormat.format_int16_dec_ML
   * @formatter(Int32.int) ConstFormat.format_int32_dec_ML
   * @formatter(Int64.int) ConstFormat.format_int64_dec_ML
   * @formatter(Word8.word) ConstFormat.format_word8_hex_ML
   * @formatter(Word16.word) ConstFormat.format_word16_hex_ML
   * @formatter(Word32.word) ConstFormat.format_word32_hex_ML
   * @formatter(Word64.word) ConstFormat.format_word64_hex_ML
   * @formatter(char) ConstFormat.format_char_ML
   *)
  datatype tlint =
      (*%
       * @format(x) x
       *)
      (*% @prefix formatWithType_
       * @format(x) { "i8" +d x }
       *)
      INT8 of Int8.int
    | (*%
       * @format(x) x
       *)
      (*% @prefix formatWithType_
       * @format(x) { "i16" +d x }
       *)
      INT16 of Int16.int
    | (*%
       * @format(x) x
       *)
      (*% @prefix formatWithType_
       * @format(x) { "i32" +d x }
       *)
      INT32 of Int32.int
    | (*%
       * @format(x) x
       *)
      (*% @prefix formatWithType_
       * @format(x) { "i64" +d x }
       *)
      INT64 of Int64.int
    | (*%
       * @format(x) x
       *)
      (*% @prefix formatWithType_
       * @format(x) { "w8" +d x }
       *)
      WORD8 of Word8.word
    | (*%
       * @format(x) x
       *)
      (*% @prefix formatWithType_
       * @format(x) { "w16" +d x }
       *)
      WORD16 of Word16.word
    | (*%
       * @format(x) x
       *)
      (*% @prefix formatWithType_
       * @format(x) { "w32" +d x }
       *)
      WORD32 of Word32.word
    | (*%
       * @format(x) x
       *)
      (*% @prefix formatWithType_
       * @format(x) { "w64" +d x }
       *)
      WORD64 of Word64.word
    | (*%
       * @format(x) x
       *)
      (*% @prefix formatWithType_
       * @format(x) { "contag" +d x }
       *)
      CONTAG of Word32.word
    | (*%
       * @format(x) x
       *)
      (*% @prefix formatWithType_
       * @format(x) { "char" +d x }
       *)
      CHAR of Char.char

  (*%
   * @formatter(Real64.real) ConstFormat.format_real64_ML
   * @formatter(Real32.real) ConstFormat.format_real32_ML
   *)
  (*%
   * @prefix formatWithType_
   * @formatter(Real64.real) ConstFormat.format_real64_ML
   * @formatter(Real32.real) ConstFormat.format_real32_ML
   *)
  datatype tlconst =
      (*%
       * @format(x) x
       *)
      (*% @prefix formatWithType_
       * @format(x) { "r64" +d x }
       *)
      REAL64 of Real64.real
    | (*%
       * @format(x) x
       *)
      (*% @prefix formatWithType_
       * @format(x) { "r32" +d x }
       *)
      REAL32 of Real32.real
    | (*%
       * @format "()"
       *)
      (*% @prefix formatWithType_
       * @format "()"
       *)
      UNIT
    | (*%
       * @format "NULLPOINTER"
       *)
      (*% @prefix formatWithType_
       * @format "NULLPOINTER"
       *)
      NULLPOINTER
    | (*%
       * @format "NULLBOXED"
       *)
      (*% @prefix formatWithType_
       * @format "NULLBOXED"
       *)
      NULLBOXED
    | (*%
        @format({name, ty})
        L8{ "FOREIGNSYMBOL" 2[ +1 "(" name ")" ] }
       *)
      (*% @prefix formatWithType_
        @format({name, ty})
        L2{ L8{ "FOREIGNSYMBOL" 2[ +1 "(" name ")" ] } +1 ":" +d ty }
       *)
      FOREIGNSYMBOL of {name : string, ty : ty}

  (*%
    @formatter(string) ConstFormat.format_string_ML
    @formatter(IntInf.int) ConstFormat.format_intInf_dec_ML
   *)
  (*% @prefix formatWithType_
    @formatter(string) ConstFormat.format_string_ML
    @formatter(IntInf.int) ConstFormat.format_intInf_dec_ML
   *)
  datatype tlstring =
      (*%
        @format(x) x
       *)
      (*% @prefix formatWithType_
        @format(x) { "string" +d x }
       *)
      STRING of string
    | (*%
        @format(x) x
       *)
      (*% @prefix formatWithType_
        @format(x) { "intinf" +d x }
       *)
      INTINF of IntInf.int

  (*%
    @formatter(iftrue) iftrue
    @formatter(ifsome) ifsome
    @formatter(ifcons) ifcons
    @formatter(ifsingle) ifsingle
    @formatter(N0ifnotsingle) N0ifnotsingle
    @formatter(N0ifcons) N0ifcons
    @formatter(RecordLabel.Map.map) helper_RecordLabelMap
    @formatter(RecordLabel.label) RecordLabel.format_label
    @formatter(FunLocalLabel.id) FunLocalLabel.format_id
    @formatter(Types.constraint) Types.format_constraint
    @formatter(Types.oprimInfo) Types.format_oprimInfo
  *)
  (*% @prefix formatWithType_
    @formatter(iftrue) iftrue
    @formatter(ifsome) ifsome
    @formatter(ifcons) ifcons
    @formatter(ifsingle) ifsingle
    @formatter(N0ifnotsingle) N0ifnotsingle
    @formatter(N0ifcons) N0ifcons
    @formatter(RecordLabel.Map.map) helper_RecordLabelMap
    @formatter(RecordLabel.label) RecordLabel.format_label
    @formatter(FunLocalLabel.id) FunLocalLabel.format_id
    @formatter(Types.constraint) Types.format_constraint
    @formatter(format_varInfo) format_varInfo
    @formatter(format_exVarInfo) format_exVarInfo
    @formatter(Types.oprimInfo) Types.format_oprimInfo
    @formatter(cast) format_cast
   *)
  datatype tlexp =
      (*%
        @format(const * loc)
        const
       *)
      (*% @prefix formatWithType_
        @format(const * loc)
        const
       *)
      TLCONSTANT of tlconst * loc
    | (*%
        @format(int * loc)
        int
       *)
      (*% @prefix formatWithType_
        @format(int * loc)
        int
       *)
      TLINT of tlint * loc
    | (*%
        @format(string * loc)
        string
       *)
      (*% @prefix formatWithType_
        @format(string * loc)
        string
       *)
      TLSTRING of tlstring * loc
    | (*%
        @format(var * loc) var
       *)
      (*% @prefix formatWithType_
        @format(var * loc) var:format_varInfo
       *)
      TLVAR of varInfo * loc
    | (*%
        @format(var * loc) var
       *)
      (*% @prefix formatWithType_
        @format(var * loc) var:format_exVarInfo
       *)
      (* extnernal variable imported through _require *)
      TLEXVAR of exVarInfo * loc
    | (*%
        @format({argVarList:arg args, bodyTy, bodyExp, loc})
        R1{
          "fn" +d
          args:ifsingle()(,"{")
          args:N0ifnotsingle()(args(arg)("," +1))
          args:ifsingle()(,"}")
          +d "=>" +1 bodyExp
        }
       *)
      (*% @prefix formatWithType_
        @ditto
       *)
      (* ty is the type of tlexp  *)
      TLFNM of
      {
        argVarList : varInfo list,
        bodyTy : ty,
        bodyExp : tlexp,
        loc : loc
      }
    | (*%
        @format({funExp, funTy, argExpList:arg args, loc})
        L8{
          funExp
          2[
            +1
            args:ifsingle()(,"{")
            args:N0ifnotsingle()(args(arg)("," +1))
            args:ifsingle()(,"}")
          ]
        }
       *)
      (*% @prefix formatWithType_
        @ditto
       *)
      (* ty is the type of the function *)
      TLAPPM of
      {
        funExp : tlexp,
        funTy : ty,
        argExpList : tlexp list,
        loc : loc
      }
    | (*%
        @format({exp, expTy, branches: rule rules, resultTy, defaultExp, loc})
        R1{
          { "switch" 2[ +1 exp +1 "of" ] }
          +1
          rules(rule)(+1 "|" +d)
          rules:ifcons()(+1 "|" +d,)
          { "_" +d "=>" +1 defaultExp }
        }
        @format:rule({const, body})
        { const +d "=>" +1 body }
       *)
      (*% @prefix formatWithType_
        @ditto
       *)
      TLSWITCH of
      {
        exp : tlexp,
        expTy : ty,
        branches : {const : tlint, body : tlexp} list,
        defaultExp : tlexp,
        resultTy : ty,
        loc : loc
      }
    | (*%
        @format({existInstMap, exp, expTy, instTyList:ty tys, loc})
        L8{
          exp
          2[
            +1
            "{" L8{ "TLDYNAMICEXISTTAPP" +1 "(" !N0{ existInstMap } ")" } "}"
          ]
        }
       *)
      (*% @prefix formatWithType_
        @format({existInstMap, exp, expTy, instTyList:ty tys, loc})
        L8{
          exp
          2[
            +1
            "{"
            !N0{
              L8{ "TLDYNAMICEXISTAPP" +1 "(" !N0{ existInstMap } ")" }
              tys:ifcons()("," +1,)
              tys(ty)("," +1)
            }
            "}"
          ]
        }
       *)
      TLDYNAMICEXISTTAPP of
      {
        existInstMap : tlexp,
        exp : tlexp,
        expTy : ty,
        instTyList : ty list,
        loc : loc
      }
    | (*%
        @format({primOp, instTyList:ty tys, argExpList: arg args, loc})
        L8{
          primOp
          2[
            +1
            args:ifsingle()(,"{")
            args:N0ifnotsingle()(args(arg)("," +1))
            args:ifsingle()(,"}")
          ]
        }
       *)
      (*% @prefix formatWithType_
        @format({primOp, instTyList:ty tys, argExpList: arg args, loc})
        L8{
          primOp
          2[
            +1
            tys:ifcons()("{",)
            tys:N0ifcons()(tys(ty)("," +1))
            tys:ifcons()("}",)
            +1
            args:ifsingle()(,"{")
            args:N0ifnotsingle()(args(arg)("," +1))
            args:ifsingle()(,"}")
          ]
        }
       *)
      TLPRIMAPPLY of
      {
        primOp : primInfo,
        instTyList : ty list,
        argExpList : tlexp list,
        loc : loc
      }
    | (*%
        @format({oprimOp, instTyList:ty tys, argExp, loc})
        L8{ oprimOp 2[ +1 argExp ] }
       *)
      (*% @prefix formatWithType_
        @format({oprimOp, instTyList:ty tys, argExp, loc})
        L8{
          oprimOp
          2[
            +1 "{" !N0{ tys(ty)("," +1) } "}"
            +1 argExp
          ]
        }
       *)
      TLOPRIMAPPLY of
      {
       oprimOp : Types.oprimInfo,
       instTyList : ty list,
       argExp : tlexp,
       loc : loc
      }
    | (*%
        @format({fields: field fields, recordTy, loc})
        "{" !N0{ fields(field) } "}"
       *)
      (*% @prefix formatWithType_
        @ditto
       *)
      TLRECORD of
      {
        fields : tlexp RecordLabel.Map.map,
        recordTy : ty RecordLabel.Map.map,
        loc : loc
      }
    | (*%
        @format({label, recordExp, recordTy, resultTy, loc})
        L8{ "#" label 2[ +1 recordExp ] }
       *)
      (*% @prefix formatWithType_
        @ditto
       *)
      TLSELECT of
      {
        label : RecordLabel.label,
        recordExp : tlexp,
        recordTy : ty,
        resultTy : ty,
        loc : loc
      }
    | (*%
        @format({label, recordExp, recordTy, elementExp, elementTy, loc})
        L8{
          recordExp
          2[
            +1 "#" +d "{"
            !N0{ label +d "=" 2[ +1 elementExp ] }
            "}"
          ]
        }
       *)
      (*% @prefix formatWithType_
        @ditto
       *)
      TLMODIFY of
      {
        label : RecordLabel.label,
        recordExp : tlexp,
        recordTy : ty,
        elementExp : tlexp,
        elementTy : ty,
        loc : loc
      }
    | (*%
        @format({decl, body, loc})
        N4{!N0{
          { "let" 2[ +1 decl ] +1 "in" } +1 body
        }}
       *)
      (*% @prefix formatWithType_
        @ditto
       *)
      TLLET of
      {
        decl : tldecl,
        body : tlexp,
        loc : loc
      }
    | (*%
        @format({exp, resultTy, loc})
        L7{ "raise" 2[ +1 exp ] }
       *)
      (*% @prefix formatWithType_
        @ditto
       *)
      TLRAISE of
      {
        exp : tlexp,
        resultTy : ty,
        loc : loc
      }
    | (*%
        @format({exp, exnVar, handler, resultTy, loc})
        R1{ exp +1 "handle" +d exnVar +d "=>" 2[ +1 handler ] }
       *)
      (*% @prefix formatWithType_
        @ditto
       *)
      (*
        handle (exp1, x, exp2)
         exp1 the expression to be evaluated normally
            x variable to received exception value
         exp2 the handler body using x
       *)
      TLHANDLE of
      {
        exp : tlexp,
        exnVar : varInfo,
        handler : tlexp,
        resultTy : ty,
        loc : loc
      }
    | (*%
        @format({catchLabel, argExpList: arg args, resultTy, loc})
        L8{
          "TLTHROW"
          +1 catchLabel
          +1
          args:ifsingle()(,"{")
          args:N0ifnotsingle()(args(arg)("," +1))
          args:ifsingle()(,"}")
        }
       *)
      (*% @prefix formatWithType_
        @ditto
       *)
      (* lightweight exception that never unwind call stack *)
      TLTHROW of
      {
        catchLabel : FunLocalLabel.id,
        argExpList : tlexp list,
        resultTy : ty,
        loc : loc
      }
    | (*%
        @format({catchLabel, argVarList: arg args, catchExp, tryExp, resultTy,
                 loc})
        R1{
          tryExp
          +1
          "TLCATCH"
          +d catchLabel +d
          args:ifsingle()(,"{")
          args:N0ifnotsingle()(args(arg)("," +1))
          args:ifsingle()(,"}")
          +d "=>" 2[ +1 catchExp ]
        }
       *)
      (*% @prefix formatWithType_
        @ditto
       *)
      (* lightweight exception that never unwind call stack *)
      TLCATCH of
      {
        catchLabel : FunLocalLabel.id,
        argVarList : varInfo list,
        catchExp : tlexp,
        tryExp : tlexp,
        resultTy : ty,
        loc : loc
      }
    | (*%
        @format({btvEnv, constraints, expTyWithoutTAbs, exp, loc})
        exp
       *)
      (*% @prefix formatWithType_
        @format({btvEnv, constraints, expTyWithoutTAbs, exp, loc})
        "[" !N0{ { btvEnv } "." +1 exp } "]"
       *)
      (* \forall t.e;  ty is the type of tlexp without type abstraction *)
      TLPOLY of
      {
        btvEnv : btvEnv,
        constraints : Types.constraint list,
        expTyWithoutTAbs : ty,
        exp : tlexp,
        loc : loc
      }
    | (*%
        @format({exp, expTy, instTyList:ty tys, loc})
        exp
       *)
      (*% @prefix formatWithType_
        @format({exp, expTy, instTyList:ty tys, loc})
        L8{ exp 2[ +1 "{" !N0{ tys(ty)("," +1) } "}" ] }
       *)
      (* TLTAPP(ex,ty1,tyl) : ty1 is the polytype, tyl are type args *)
      TLTAPP of
      {
        exp : tlexp,
        expTy : ty,
        instTyList : ty list,
        loc : loc
      }
    | (*%
        @format({funExp, argExpList: arg args, attributes,
                 resultTy: retTy retTyOpt, loc})
        L8{
          "TLFOREIGNAPPLY"
          2[
            +1 funExp
            +1 "{" !N0{ args(arg)("," +1) } "}"
          ]
        }
       *)
      (*%
        @prefix formatWithType_
        @format({funExp, argExpList: arg args, attributes,
                 resultTy: retTy retTyOpt, loc})
        L2{
          L8{
            "TLFOREIGNAPPLY"
            2[
              +1 funExp
              +1 "{" !N0{ args(arg)("," +1) } "}"
            ]
          }
          +1 ":" +d retTyOpt:ifsome()(retTyOpt(retTy), "()")
        }
       *)
      TLFOREIGNAPPLY of
      {
        funExp : tlexp,
        argExpList : tlexp list,
        attributes : FFIAttributes.attributes,
        resultTy : ty option,
        loc : loc
      }
    | (*%
        @format({attributes, argVarList:arg args, resultTy, bodyExp, loc})
        R1{
          "TLCALLBACKFN" +d
          args:ifsingle()(,"{")
          args:N0ifnotsingle()(args(arg)("," +1))
          args:ifsingle()(,"}")
          +d "=>" +1 bodyExp
        }
       *)
      (*% @prefix formatWithType_
        @format({attributes, argVarList:arg args, resultTy: retTy retTyOpt,
                 bodyExp, loc})
        R1{
          "TLCALLBACKFN" +d
          args:ifsingle()(,"{")
          args:N0ifnotsingle()(args(arg)("," +1))
          args:ifsingle()(,"}")
          +d "=>" +1
          L2{ bodyExp +1 ":" +d retTyOpt:ifsome()(retTyOpt(retTy),"()") }
        }
       *)
      TLCALLBACKFN of
      {
        attributes : FFIAttributes.attributes,
        argVarList : varInfo list,
        bodyExp : tlexp,
        resultTy : ty option,
        loc : loc
      }
    | (*%
        @format({exp, expTy, targetTy, cast, loc})
        L8{ cast 2[ 1 "(" !N0{ exp } ")" ] }
       *)
      (*% @prefix formatWithType_
        @format({exp, expTy, targetTy, cast, loc})
        L2{
          L8{ cast 2[ 1 "(" !N0{ exp } ")" ] }
          +1 ":" +d targetTy
        }
       *)
      (* cast e to some type ty; used to coerce con type to a record type *)
      TLCAST of
      {
        exp : tlexp,
        expTy : ty,
        targetTy : ty,
        cast : cast,
        loc : loc
      }
    | (*%
        @format({ty, loc})
        L8{ "TLSIZEOF" 2[ +1 "(" !N0{ ty } ")" ] }
       *)
      (*% @prefix formatWithType_
        @ditto
       *)
      TLSIZEOF of
      {
        ty : ty,
        loc : loc
      }
    | (*%
        @format({recordTy, label, loc})
        L8{ "TLINDEXOF" 2[ +1 "(" !N0{ recordTy "," +1 label } ")" ] }
       *)
      (*% @prefix formatWithType_
        @ditto
       *)
      TLINDEXOF of
      {
        recordTy : ty,
        label : RecordLabel.label,
        loc : loc
      }
    | (*%
        @format({ty, loc})
        L2{ "_reifyTy" +1 "(" !N0{ ty } ")" }
       *)
      (*% @prefix formatWithType_
        @ditto
       *)
      TLREIFYTY of
      {
        ty : ty,
        loc : loc
      }

  and tldecl =
      (*%
        @format({var, exp, loc})
        { "val" +d var +d "=" 2[ +1 exp ] }
       *)
      (*% @prefix formatWithType_
        @format({var, exp, loc})
        { "val" +d var:format_varInfo +d "=" 2[ +1 exp ] }
       *)
      TLVAL of
      {
        var : varInfo,
        exp : tlexp,
        loc : loc
      }
    | (*%
        @format(bind binds * loc)
        {
          "val" +d "rec"
          binds(bind)(+1 "and")
        }
        @format:bind({var, exp})
        2[ +1 !R1{ var +d "=" 2[ +1 exp ] } ]
       *)
      (*% @prefix formatWithType_
        @format(bind binds * loc)
        {
          "val" +d "rec"
          binds(bind)(+1 "and")
        }
        @format:bind({var, exp})
        2[ +1 !R1{ var +1 +d "=" 2[ +1 exp ] } ]
       *)
      TLVALREC of {var : varInfo, exp : tlexp} list * loc
    | (*%
        @format({btvEnv, constraints, recbinds: bind binds, loc})
        {
          "val" +d "rec"
          binds(bind)(+1 "and")
        }
        @format:bind({var, exp})
        2[ +1 !R1{ var +d "=" 2[ +1 exp ] } ]
       *)
      (*% @prefix formatWithType_
        @format({btvEnv, constraints, recbinds: bind binds, loc})
        {
          "val" +d "rec" +d "[" !N0{ btvEnv } "]"
          binds(bind)(+1 "and")
        }
        @format:bind({var, exp})
        2[ +1 !R1{ var +d "=" 2[ +1 exp ] } ]
       *)
      TLVALPOLYREC of
      {
        btvEnv : btvEnv,
        constraints : Types.constraint list,
        recbinds : {var:varInfo, exp:tlexp} list,
        loc : loc
      }
    | (*%
        @format({weak, var, exp})
        { "export" weak:iftrue()(+d "weak",) +d var +d "=" 2[ +1 exp ] }
       *)
      (*% @prefix formatWithType_
        @ditto
       *)
      TLEXPORTVAR of
      {
        weak : bool,
        var : exVarInfo,
        exp : tlexp
      }
    | (*%
        @format(var * provider)
        "extern" +d var
       *)
      (*% @prefix formatWithType_
        @format(var * provider)
        "extern" +d var
       *)
      TLEXTERNVAR of exVarInfo * Types.provider

end
